package nodeutils;

#########################################################################
#									#
# Name:	 	nodeutils.pm (Perl Module)				#
#									#
# Completed:	24th March, 2004.					#
#									#
# Updated:	13th January, 2005.					#
#									#
# Author:	Simon Edwards, Linuxha.net				#
#									#
# Arguments:	N/A							#
#									#
# Purpose:	This module provides the routines that are used to	#
#		determine which node an application should run on,	#
#		as well as providing details of expected other		#
#		applications that should be running prior to this	#
#		application.						#
#									#
# Limitations:	1 - Lots probably.					#
#									#
# Environment:	None.							#
#									#
# Exit Codes:	N/A							#
# 									#
# Version:	@(#)1.0.0 Original (SE)>				#
# 		@(#)1.0.1 Additional warnings and corrections (SE)>	#
# 		@(#)1.0.2 Modified to provide 0119 fix (SE)>		#
# 		@(#)1.0.3 Support LEAST_CPU_LOAD - 0159 (SE)>		#
# 		@(#)1.0.4 Fixed 0180 (SE)>				#
#									#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#									#
# Complete SOCM Version History						#
#									#
# Version:	1.0.3							#
# Date:		7th January, 2005.					#
# Author:	Simon Edwards, Proprius Consulting Ltd.			#
# Change:	Support LEAST_CPU_LOAD attempting to choose the		#
#		node with not the least number of applications, but	#
#		the lowest CPU load.					#
#									#
#########################################################################

use Sys::Hostname;
use Data::Dumper;

#########################################################################
# We expect to be passed a reference to the cldaemon info...		#
# GLOBALS we will take advantage off:					#
# ::logmsg, ::logwarn, ::errmsg - logging functions.			#
# ::cld_client ::OTHER_NODE_STATE, ::OTHER_NODE_NAME			#
#########################################################################

sub new {
my $proto=shift;
my $class=ref($proto) || $proto;
my $appmap_info=shift;
my $self={};

	if(!defined($appmap_info)) {
		return undef;
	}

	$self->{APPINFO}=$appmap_info;
	bless($self,$class);
	return $self;
}

#########################################################################
# The choose_node expects the following arguments:			#
# APP = X								#
# It will return the name of the node to use to start the application.	#
# This routine bases the result based on the PREFERRED_NODE option for	#
# the application, and the status of the cldaemon on the remote node.	#
# It does not take account of the validity of the data, so the		#
# node returned might not have a valid copy of data and may rely on	#
# the copy on the remote node.						#
# 									#
# Note the clrunapp will eventually call clstartapp which might not	#
# be able to start the application on the specified node - returning	#
# a node does not guarantee it will actually start on that node or not.	#
# Result: (NODE_NAME,0,"") or (undef,errorcode,string).			#
# 									#
# This function also takes into account the fail-over settings for	#
# the node - so a preferred node is not the returned node if not	#
# currently specified as valid for this application.			#
#########################################################################

sub choose_node {
my $self=shift;
my %args=@_;
my ($chostname,@vn,$lcount,$rcount,$prefnode,$app,$xml,$x);

	$chostname=hostname;
	if(!exists($args{APP})) {
		return (undef,2,"Missing required APP argument.");
	}
	$app=$args{APP};

	#################################################################
	# We validate that this application exists first.		#
	#################################################################
	if(!exists($self->{APPINFO}->{$app})) {
		return (undef,1,"Specified application is not registered.");
	}

	#################################################################
	# If the validnodes list does not contain both nodes then we	#
	# simply return the details for the valid node, no matter	#
	# what other settings we have for the application.		#
	#################################################################

	@vn=split(/,/,$self->{APPINFO}->{$app}->{VALIDNODES});
	if(scalar(@vn)==1) {
		return($self->{APPINFO}->{$app}->{VALIDNODES},0,"");
	}
	if(scalar(@vn)==0) {
		return("",0,"");
	}

	#################################################################
	# Shortcut here - if ::OTHER_NODE_STATE ne "UP" then we		#
	# return this node!						#
	#################################################################
	if($::OTHER_NODE_STATE ne "UP") {
		return($chostname,0,"");
	}

	$xml=$self->{APPINFO}->{$app}->{XMLINFO};

	#################################################################
	# Get to see if a preferred_node option exists in globals.	#
	# If it does then we get it and validate it - defaulting to	#
	# current host if the value is not suitable.			#
	#################################################################

	if(exists($xml->{global}->{preferred_node})) {
		$prefnode=$xml->{global}->{preferred_node};

		#########################################################
		# Ensure it is a valid node, or LEAST_LOAD.		#
		#########################################################
		if(!scalar(grep {/^$prefnode$/} ("LEAST_LOAD","LEAST_CPU_LOAD","LEAST_APP_LOAD",$chostname,$::OTHER_NODE_NAME))) {
			::logwarn("PREFERRED_NODE setting for application $app ignored -");
			::logwarn("value \"$prefnode\" is not valid.");
			return($chostname,0,"");
		}
	} else {
		::logwarn("PREFERRED_NODE setting not defined for application $app");
		return($chostname,0,"");
	}

	#################################################################
	# If prefnode is set, then now we do some processing and 	#
	# return what we believe is the best value for it.		#
	#################################################################

	if($prefnode eq $chostname) {
		return ($prefnode,0,"");
	}
	if($prefnode eq $::OTHER_NODE_NAME) {
		return ($prefnode,0,"");
	}

	#################################################################
	# If we get here we have LEAST_LOAD or LEAST_CPU_LOAD option,so	#
	# we get the LOADAVERAGE from the remote node if we need it.	#
	#################################################################
	
	if($prefnode eq "LEAST_CPU_LOAD") {
		my ($fd,$resp,$cline,@F1,@F2,@F3,@F4);
		if(defined($::cld_client)) {
			$resp=$::cld_client->send_msg(MSG => "LOADAVERAGE", TIMEOUT => 10);
			$::cld_client->close_session;
			if(!defined($resp) || $resp eq "ERROR") {
				::logwarn("Remote daemon did not respond correctly to LOADAVERAGE request.");
			} else {
				if(open($fd,"/proc/loadavg")) {
					$cline=<$fd>; chomp $fd;

					#################################
					# Now compare the set sets.	#
					#################################
					@F1=split(/\s+/,$cline);
					@F2=split(/\s+/,$resp);
					if($F1[2] < $F2[2]) {
						return($chostname,0,"");
					} elsif($F2[2] < $F1[2]) {
						return($::OTHER_NODE_NAME,0,"");
					}
					#################################
					# Load averages the same - so	#
					# check number of processes.	#
					#################################
					@F3=split(/\//,$F1[3]);
					@F4=split(/\//,$F2[3]);
					if($F3[1] < $F4[1]) {
						return($chostname,0,"");
					} else {
						return($::OTHER_NODE_NAME,0,"");
					}
				close($fd);
				}
			}
		}
	}

	#################################################################
	# Everything below is LEAST_LOAD (aka LEAST_APP_LOAD).		#
	# Also get here if the LEAST_CPU_LOAD stuff failed.		#
	#################################################################

	$lcount=$rcount=0;
	foreach (keys(%{$self->{APPINFO}})) {
		if(exists($self->{APPINFO}->{$_}->{NODE})) {
			if($self->{APPINFO}->{$_}->{NODE} eq $chostname) {
				$lcount++;
			}
			if($self->{APPINFO}->{$_}->{NODE} eq $::OTHER_NODE_NAME) {
				$rcount++;
			}
		}
	}
	if($lcount==$rcount || $lcount<$rcount) {
		return ($chostname,0,"");
	}
	return ($::OTHER_NODE_NAME,0,"");
}

#########################################################################
# This routine will return a list of applications that should be	#
# started in the cluster - ordered by priority.				#
# Where 1 is highest priority. 						#
# Please note that dependencies are not taken into account as yet -	#
# this will be done later.						#
#########################################################################

sub orderlist {
my $self=shift;
my ($capp);
my (@applist,$ap);

	foreach $capp (keys(%{$self->{APPINFO}})) {
		if(!exists($self->{APPINFO}->{$capp}->{XMLINFO}->{global}->{priority})) {
			$self->{APPINFO}->{$capp}->{XMLINFO}->{global}->{priority}=100;
		}
	}

	@ap=(keys(%{$self->{APPINFO}}));
	@applist= sort {
		$self->{APPINFO}->{$a}->{XMLINFO}->{global}->{priority} <=> 
			$self->{APPINFO}->{$b}->{XMLINFO}->{global}->{priority}
	} @ap;
	return @ap;
}

#########################################################################
# This function is recursive and returns a list of dependents for the	#
# specified application, or an empty list if no dependents or any	#
# recursion is spotted.							#
#########################################################################

sub getdependents {
my $self=shift;
my $capp=shift;
my @clist=@_;

	if(!exists($self->{APPINFO}->{$capp})) {
		return (@clist);
	}
	@clist=() if !defined($clist[0]);
	# ::logmsg("capp=$capp,clist=".join(",",@clist));

	if($self->{APPINFO}->{$capp}->{XMLINFO}->{global}->{dependencies}) {
		foreach $capp2 (split(/,/,$self->{APPINFO}->{$capp}->{XMLINFO}->{global}->{dependencies})) {
			next if $capp2 =~ /^\s+$/;
			# Skip over elements that exist already! #
			next if(scalar(grep {/^$capp2$/} @clist));
			# ::logmsg("Getting dependencies for $capp2...");
			@clist=($capp2,@clist);
			@clist=(getdependents($self,$capp2,@clist));
			# ::logmsg("2capp=$capp,clist=".join(",",@clist));
		}
	}
	# ::logmsg("Return:".join(",",@clist));
	return (@clist);
}

# routines to show priority of applications, and dependencies
# when using clrunapp or clform now...
1;

